package com.smy.bc.fabric.core.configuration;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.NetUtil;
import com.smy.bc.fabric.core.HFUser;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.TransactionException;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric_ca.sdk.EnrollmentRequest;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import org.hyperledger.fabric_ca.sdk.RegistrationRequest;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Properties;

/**
 * <p>Title: FabricConfiguration</p>
 * <p>Description: </p>
 *
 * @author zhaofeng
 * @version 1.0
 * @date 18-9-12
 * //
 */
@Component
@ConfigurationProperties(prefix = "blockchain")
@Slf4j
@Data
public class FabricConfiguration {

    private String name = "account";
    private CAClient caClient = new CAClient();
    private Organization organization = new Organization();
    private String channelName ="mychannel";
    private List<String> channelPeers = CollUtil.newArrayList("grpc://127.0.0.1:7051","grpc://127.0.0.1:7056");
    private List<String> channelOrders = CollUtil.newArrayList("grpc://127.0.0.1:7050") ;
    private List<String> channelEvents = CollUtil.newArrayList("grpc://127.0.0.1:7053","grpc://127.0.0.1:7058");

    @Bean
    public HFClient hfClient() {

        try {

            // create fabric-ca client

            HFCAClient hfCaClient = getHfCaClient(caClient);

            // enroll or load admin
            HFUser admin = getAdmin(hfCaClient);
            log.info(admin.toString());

            // register and enroll new user
            HFUser hfUser = getUser(hfCaClient, admin, organization.getUser());
            log.info(hfUser.toString());

            // get HFC client instance
            HFClient client = getHfClient();
            // set user context
            client.setUserContext(admin);

            // get HFC channel using the client
            Channel channel = getChannel(client);
            log.info("Channel: " + channel.getName());

            return client;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public HFCAClient getHfCaClient(CAClient client) throws Exception {
        CryptoSuite cryptoSuite = CryptoSuite.Factory.getCryptoSuite();
        HFCAClient caClient = HFCAClient.createNewInstance(client.getUrl(), client.caClientProperties);
        caClient.setCryptoSuite(cryptoSuite);
        return caClient;
    }

    public HFUser getAdmin(HFCAClient hfcaClient) throws Exception {
        HFUser admin = tryDeserialize(caClient.getAdminUser());
        if (admin == null) {

            EnrollmentRequest enrollmentRequest = new EnrollmentRequest();

            if(caClient.getTlsEnable()){

                //This shows how to get a client TLS certificate from Fabric CA
                // we will use one client TLS certificate for orderer peers etc.
                //TODO 这里可配置
                enrollmentRequest.addHost(NetUtil.getIpByHost(caClient.getUrl()));
                enrollmentRequest.setProfile("tls");
            }
            Enrollment adminEnrollment = hfcaClient.enroll(caClient.getAdminUser(), caClient.getAdminSecret(),enrollmentRequest);
            admin = new HFUser(caClient.getAdminUser(), organization.getAffiliation(), organization.getMspid(), adminEnrollment);
            serialize(admin);
        }

        return admin;
    }

    public Channel getChannel(HFClient client) throws InvalidArgumentException, TransactionException {
        // initialize channel
        Channel channel = client.newChannel(channelName);

        for (int i = 0; i < getChannelPeers().size(); i++) {
            // peer name and endpoint in fabcar network
            Peer peer = client.newPeer("peer"+i, getChannelPeers().get(i));
            channel.addPeer(peer);

        }

        for (int i = 0; i < getChannelOrders().size(); i++) {
            Orderer orderer = client.newOrderer("orderer"+i, getChannelOrders().get(i));
            // channel name in fabcar network
            channel.addOrderer(orderer);
        }

        for (int i = 0; i < getChannelEvents().size(); i++) {
            // eventhub name and endpoint in fabcar network
            EventHub eventHub = client.newEventHub("eventhub"+i, getChannelEvents().get(i));
            channel.addEventHub(eventHub);
        }
        channel.initialize();
        return channel;
    }

    public HFClient getHfClient() throws Exception {
        // initialize default cryptosuite
        CryptoSuite cryptoSuite = CryptoSuite.Factory.getCryptoSuite();
        // setup the client
        HFClient client = HFClient.createNewInstance();
        client.setCryptoSuite(cryptoSuite);
        return client;
    }

    public HFUser getUser(HFCAClient caClient, HFUser registrar, String userId) throws Exception {
        HFUser hfUser = tryDeserialize(userId);
        if (hfUser == null) {
            RegistrationRequest rr = new RegistrationRequest(userId, organization.getAffiliation());
            String enrollmentSecret = caClient.register(rr, registrar);
            Enrollment enrollment = caClient.enroll(userId, enrollmentSecret);
            hfUser = new HFUser(userId, organization.getAffiliation(), organization.getMspid(), enrollment);
            serialize(hfUser);
        }
        return hfUser;
    }

    public void serialize(HFUser hfUser) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(
                Paths.get(hfUser.getName() + ".tail")))) {
            oos.writeObject(hfUser);
        }
    }

    public HFUser tryDeserialize(String name) throws Exception {
        if (Files.exists(Paths.get(name + ".tail"))) {
            return deserialize(name);
        }
        return null;
    }

    public HFUser deserialize(String name) throws Exception {
        try (ObjectInputStream decoder = new ObjectInputStream(
                Files.newInputStream(Paths.get(name + ".tail")))) {
            return (HFUser) decoder.readObject();
        }
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class CAClient {

        private String url ="http://127.0.0.1:7054";
        private Boolean tlsEnable = false;
        private Properties caClientProperties;
        private String adminUser = "admin";
        private String adminSecret = "adminpw";
    }


    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Organization {
        private String name="peerOrg1";
        private String user ="user1";
        private String mspid="Org1MSP";
        private String affiliation ="org1";
    }

}
